use std::fs;
use std::fs::File;
use std::path::PathBuf;
use std::collections::{HashMap, HashSet};
use std::io::{BufRead, BufReader, BufWriter, Write};
use memmap2::Mmap;
use color_eyre::eyre::{Result, WrapErr};
use color_eyre::eyre;
use crate::models::*;
use crate::repo::RepoRevise;
use crate::package;

#[derive(Debug)]
pub struct FileMapper {
    file: File,
    mmap: Mmap,
}

impl FileMapper {
    pub fn new(file_path: &str) -> std::io::Result<Self> {
        let file = File::open(file_path)?;
        // Memory map the file (unsafe because we must ensure the file isn't modified externally)
        let mmap = unsafe { Mmap::map(&file)? };
        Ok(Self { file, mmap })
    }

    /// Get the entire mapped data
    pub fn data(&self) -> &[u8] {
        &self.mmap
    }

    /// Get a specific range of the mapped data
    /// Panics if range is out of bounds
    pub fn range(&self, range: &PackageRange) -> &[u8] {
        &self.mmap[range.begin..(range.begin + range.len)]
    }

    /// Safe range access with bounds checking
    pub fn checked_range(&self, range: &PackageRange) -> Option<&[u8]> {
        if range.begin + range.len <= self.mmap.len() {
            Some(&self.range(range))
        } else {
            None
        }
    }
}

// // Example usage
// fn main() -> std::io::Result<()> {
//     let mapper = FileMapper::new("example.txt")?;
//
//     // Access first 100 bytes
//     if let Some(data) = mapper.checked_range(0..100) {
//         println!("First 100 bytes: {:?}", data);
//     }
//
//     // Process the entire file in chunks
//     let chunk_size = 4096;
//     for chunk in mapper.data().chunks(chunk_size) {
//         // Process each chunk
//         println!("Chunk length: {}", chunk.len());
//     }
//
//     Ok(())
// }

/// Deserializes essential package names from a file
pub fn deserialize_repoindex(file_path: &PathBuf) -> Result<RepoIndex> {
    let contents = fs::read_to_string(&file_path)
        .with_context(|| format!("Failed to read file: {}", file_path.display()))?;

    let repoindex: RepoIndex = serde_json::from_str(&contents)
        .with_context(|| format!("Failed to parse JSON from file: {}", file_path.display()))?;

    Ok(repoindex)
}

/// Get standard package-related paths based on a base packages path
pub fn get_package_paths(repo_dir: &PathBuf, packages_filename: &str) -> (PathBuf, PathBuf, PathBuf, PathBuf) {
    let packages_path = repo_dir.join(packages_filename);
    let provide2pkgnames_path = repo_dir.join(packages_filename.replace("packages", "provide2pkgnames")).with_extension("yaml");
    let essential_pkgnames_path = repo_dir.join(packages_filename.replace("packages", "essential_pkgnames"));
    let pkgname2ranges_path = packages_path.with_extension("idx");

    (packages_path, provide2pkgnames_path, essential_pkgnames_path, pkgname2ranges_path)
}

pub fn populate_repoindex_data(repo: &RepoRevise, mut repo_index: RepoIndex) -> Result<()> {
    let repo_dir = crate::dirs::get_repo_dir(&repo)?;

    // Check if we need to load provide2pkgnames (expensive dependency resolution data)
    // Skip for list command since it doesn't need dependency resolution
    let load_provides = crate::models::config().subcommand != "list";

    for (_, shard) in &mut repo_index.repo_shards {
        let filename = shard.packages.filename.clone();
        let (packages_path, provide2pkgnames_path, essential_pkgnames_path, pkgname2ranges_path) =
            get_package_paths(&repo_dir, &filename);
        shard.packages_mmap = Some(FileMapper::new(packages_path.to_str().unwrap())?);
        shard.pkgname2ranges = deserialize_pkgname2ranges(&pkgname2ranges_path)?;

        // Conditionally load provide2pkgnames only when needed
        if load_provides {
            shard.provide2pkgnames = deserialize_provide2pkgnames(&provide2pkgnames_path)?;
        } else {
            // Keep empty for list command
            shard.provide2pkgnames = std::collections::HashMap::new();
        }

        shard.essential_pkgnames = deserialize_essential_pkgnames(&essential_pkgnames_path)?;
    }
    {
        let mut repodata_indice = repodata_indice_mut();
        repodata_indice.insert(repo.repodata_name.clone(), repo_index);
    }
    Ok(())
}

/// Serializes essential package names to a file
pub fn serialize_essential_pkgnames(path: &PathBuf, pkgnames: &HashSet<String>) -> Result<()> {
    let file = File::create(path)?;
    let mut writer = BufWriter::new(file);

    let mut sorted_names: Vec<_> = pkgnames.iter().collect();
    sorted_names.sort();

    for item in sorted_names {
        writeln!(writer, "{}", item)?;
    }

    Ok(())
}

/// Deserializes essential package names from a file
pub fn deserialize_essential_pkgnames(file_path: &PathBuf) -> Result<HashSet<String>> {
    let file = File::open(file_path)?;
    let reader = BufReader::new(file);
    let mut hashset: HashSet<String> = HashSet::new();

    for line in reader.lines() {
        let line = line?;
        hashset.insert(line);
    }

    Ok(hashset)
}

/// Serializes package provides mapping to a file
pub fn serialize_provide2pkgnames(path: &PathBuf, provide2pkgnames: &HashMap<String, Vec<String>>) -> Result<()> {
    let file = File::create(path)?;
    let mut writer = BufWriter::new(file);

    let mut sorted_names: Vec<_> = provide2pkgnames.iter().collect();
    sorted_names.sort_by(|a, b| a.0.cmp(&b.0));

    for (key, values) in sorted_names {
        let line = format!("{}: {}", key, values.join(" "));
        writeln!(writer, "{}", line)?;
    }

    Ok(())
}

/// Deserializes package provides mapping from a file
pub fn deserialize_provide2pkgnames(file_path: &PathBuf) -> Result<HashMap<String, Vec<String>>> {
    let file = File::open(file_path)?;
    let reader = BufReader::new(file);
    let mut map: HashMap<String, Vec<String>> = HashMap::new();

    for (line_num, line_result) in reader.lines().enumerate() {
        let line = line_result.context(format!("Failed to read line {} from {}", line_num + 1, file_path.display()))?;
        if let Some((key, values)) = line.split_once(": ") {
            let values: Vec<String> = values.split(" ").map(|s| s.to_string()).collect();
            map.insert(key.to_string(), values);
        }
    }

    Ok(map)
}

// Function to serialize pkgname2ranges to a file
pub fn serialize_pkgname2ranges(path: &PathBuf, pkgname2ranges: &HashMap<String, Vec<PackageRange>>) -> Result<()> {
    let mut file = fs::File::create(path)
        .with_context(|| format!("Failed to create index file: {}", path.display()))?;

    // Sort package names before writing
    let mut sorted_packages: Vec<_> = pkgname2ranges.iter().collect();
    sorted_packages.sort_by(|a, b| a.0.cmp(b.0));

    for (pkgname, offsets) in sorted_packages {
        let offset_str = offsets.iter()
            .map(|o| format!("{:x} {:x}", o.begin, o.len))
            .collect::<Vec<_>>()
            .join(" ");
        writeln!(file, "{}: {}", pkgname, offset_str)
            .with_context(|| format!("Failed to write to index file: {}", path.display()))?;
    }
    Ok(())
}

// Function to deserialize pkgname2ranges from a file
pub fn deserialize_pkgname2ranges(path: &PathBuf) -> Result<HashMap<String, Vec<PackageRange>>> {
    let content = fs::read_to_string(path)
        .with_context(|| format!("Failed to read index file: {}", path.display()))?;

    let mut pkgname2ranges = HashMap::new();
    for line in content.lines() {
        if let Some((pkgname, offsets_str)) = line.split_once(": ") {
            let offsets: Vec<PackageRange> = offsets_str
                .split_whitespace()
                .collect::<Vec<_>>()
                .chunks(2)
                .filter_map(|chunk| {
                    if chunk.len() == 2 {
                        let begin = usize::from_str_radix(chunk[0], 16).ok()?;
                        let len = usize::from_str_radix(chunk[1], 16).ok()?;
                        Some(PackageRange {
                            begin,
                            len,
                        })
                    } else {
                        None
                    }
                })
                .collect();
            if !offsets.is_empty() {
                pkgname2ranges.insert(pkgname.to_string(), offsets);
            }
        }
    }
    Ok(pkgname2ranges)
}

pub fn deserialize_package(paragraph: &str) -> Result<Package> {
    let mut package = Package {
        pkgname: String::new(),
        version: String::new(),
        arch: String::new(),
        size: 0,
        installed_size: 0,
        build_time: None,
        source: None,
        location: String::new(),

        // caHash is only available in installed epkg_store/fs/package.txt,
        // when the struct is loaded by map_pkgline2package()
        ca_hash: None,

        // Apk only has sha1sum; other formats only have sha256sum
        sha256sum: None,
        sha1sum: None,

        depends: Vec::new(),
        requires_pre: Vec::new(),
        requires: Vec::new(),
        provides: Vec::new(),
        recommends: Vec::new(),
        suggests: Vec::new(),
        conflicts: Vec::new(),
        summary: String::new(),
        description: None,
        homepage: String::new(),
        section: None,
        priority: None,
        maintainer: String::new(),
        tag: None,
        origin_url: None,
        pkgkey: String::new(),
        repodata_name: String::new(),
        package_baseurl: String::new(),
    };

    // Track the current key and value for multi-line handling
    let mut current_key = String::new();
    let mut current_value = String::new();

    for line in paragraph.lines() {
        if let Some((key, value)) = line.split_once(": ") {
            // If we have a previous key/value pair, process it before starting a new one
            if !current_key.is_empty() {
                process_key_value(&mut package, &current_key, &current_value);
                current_key.clear();
                current_value.clear();
            }

            current_key = key.trim().to_string();
            current_value = value.trim().to_string();
        } else if line.starts_with(" ") && !current_key.is_empty() {
            // This is a continuation line (indented follow-up line)
            // Add it to the current value with a newline
            current_value.push('\n');
            current_value.push_str(line.trim());
        }
    }

    // Process the last key/value pair if any
    if !current_key.is_empty() {
        process_key_value(&mut package, &current_key, &current_value);
    }
    if package.location.is_empty() { // APKINDEX misses location field
        package.location = format!("{}-{}.apk", package.pkgname, package.version);
    }
    package.pkgkey = package::format_pkgkey(&package.pkgname, &package.version, &package.arch);

    Ok(package)
}

// Helper function to process a key/value pair
fn process_key_value(package: &mut Package, key: &str, value: &str) {
    match key {
        "pkgname"           => package.pkgname      = value.to_string(),
        "version"           => package.version      = value.to_string(),
        "arch"              => package.arch         = value.to_string(),
        "summary"           => package.summary      = value.to_string(),
        "description"       => package.description  = Some(value.to_string()),
        "location"          => package.location     = value.to_string(),
        "homepage"          => package.homepage     = value.to_string(),
        "maintainer"        => package.maintainer   = value.to_string(),
        "section"           => package.section      = Some(value.to_string()),
        "priority"          => package.priority     = Some(value.to_string()),
        "size"              => if let Ok(size)      = value.parse() { package.size = size; },
        "installedSize"     => if let Ok(size)      = value.parse() { package.installed_size = size; },
        "buildTime"         => if let Ok(time)      = value.parse() { package.build_time = Some(time); },
        "sha256"            => package.sha256sum    = Some(value.to_string()),
        "sha1"              => package.sha1sum      = Some(value.to_string()),
        "tag"               => package.tag          = Some(value.to_string()),
        "requiresPre"       => package.requires_pre = value.split(", ").map(|s| s.to_string()).collect(),
        "requires"          => package.requires     = value.split(", ").map(|s| s.to_string()).collect(),
        "provides"          => package.provides     = value.split(", ").map(|s| s.to_string()).collect(),
        "recommends"        => package.recommends   = value.split(", ").map(|s| s.to_string()).collect(),
        "suggests"          => package.suggests     = value.split(", ").map(|s| s.to_string()).collect(),
        "conflicts"         => package.conflicts    = value.split(", ").map(|s| s.to_string()).collect(),
        "source"            => package.source       = Some(value.to_string()),
        "originUrl"         => package.origin_url   = Some(value.to_string()),
        _                   => {
            // Unknown field, ignore or log
        }
    }
}

pub fn lookup_in_packages(
    pkgname: &str,
    repodata_name: &str,
    package_baseurl: &str,
    pkgname2ranges: &HashMap<String, Vec<PackageRange>>,
    packages_mmap: &Option<FileMapper>
) -> Result<Vec<Package>> {
    let mut packages = Vec::new();

    if let Some(ranges) = pkgname2ranges.get(pkgname) {
        if let Some(mmap) = packages_mmap {
            for range in ranges {
                if let Some(data) = mmap.checked_range(&range) {
                    if let Ok(paragraph) = std::str::from_utf8(data) {
                        if let Ok(mut package) = deserialize_package(paragraph) {
                            package.repodata_name = repodata_name.to_string();
                            package.package_baseurl = package_baseurl.to_string();
                            packages.push(package);
                        }
                    }
                }
            }
        }
    }

    Ok(packages)
}

pub fn map_pkgname2packages(pkgname: &str) -> Result<Vec<Package>> {
    let mut packages = Vec::new();

    let repodata_indice = repodata_indice();
    for repo_index in repodata_indice.values() {
        for shard in repo_index.repo_shards.values() {
            if let Ok(mut shard_packages) = lookup_in_packages(pkgname,
                        &repo_index.repodata_name,
                        &repo_index.package_baseurl,
                        &shard.pkgname2ranges,
                        &shard.packages_mmap) {
                packages.append(&mut shard_packages);
            }
        }
    }

    Ok(packages)
}

pub fn map_provide2pkgnames(capability: &str) -> Result<Vec<String>> {
    let mut pkgnames = Vec::new();

    let repodata_indice = repodata_indice();
    for repo_index in repodata_indice.values() {
        for shard in repo_index.repo_shards.values() {
            if let Some(shard_pkgnames) = shard.provide2pkgnames.get(capability) {
                pkgnames.extend(shard_pkgnames.clone());
            }
        }
    }

    Ok(pkgnames)
}

pub fn get_essential_pkgnames() -> Result<HashSet<String>> {
    let mut pkgnames = HashSet::new();

    let repodata_indice = repodata_indice();
    for repo_index in repodata_indice.values() {
        for shard in repo_index.repo_shards.values() {
            pkgnames.extend(shard.essential_pkgnames.clone());
        }
    }

    Ok(pkgnames)
}

pub fn is_essential_pkgname(pkgname: &str) -> bool {
    match get_essential_pkgnames() {
        Ok(essential_pkgnames) => essential_pkgnames.contains(pkgname),
        Err(e) => {
            log::warn!("Failed to get essential package names: {}", e);
            false
        }
    }
}

/// Maps a pkgline (from installed packages) to a Package by deserializing from local store
pub fn map_pkgline2package(pkgline: &str) -> Result<Package> {
    // The pkgline should be the path/identifier for the package in the store
    // Read the package.txt file from the store directory
    let store_path = crate::models::dirs().epkg_store.join(pkgline).join("info/package.txt");

    if !store_path.exists() {
        return Err(eyre::eyre!("Package info not found in store: {}", store_path.display()));
    }

    let content = fs::read_to_string(&store_path)
        .wrap_err_with(|| format!("Failed to read package info: {}", store_path.display()))?;

    // Reuse the existing deserialize_package function
    let mut package = deserialize_package(&content)?;

    // Set a default repodata_name for locally installed packages
    package.repodata_name = "local".to_string();

    Ok(package)
}
